#include <fstream>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include "main.hpp"
using namespace std;

class ProgramWrapper {
	public:
		ProgramWrapper(void) {
		}
		~ProgramWrapper(void) {
		}
} ProgramWrapperObject;

#if defined(USING_NDS)
void OnKeyPressed(int key) {
	if(key > 0) {
		iprintf("%c", key);
	}
}
#endif

void initOratr(Environment &env)
{
#define ENVADD(function) env.add(#function,bi_##function);
	ENVADD(man);
	ENVADD(print);
	ENVADD(typeof);
	ENVADD(get);
	ENVADD(echo);
	ENVADD(set);
	ENVADD(math);
	ENVADD(rand);
	ENVADD(frand);
	ENVADD(exit);
	ENVADD(null);
	ENVADD(local);
	ENVADD(global);
	ENVADD(sin);
	ENVADD(cos);
	ENVADD(tan);
	ENVADD(asin);
	ENVADD(acos);
	ENVADD(atan);
	ENVADD(run);
	ENVADD(import);
	ENVADD(alias);
	ENVADD(call);
	ENVADD(time);
	ENVADD(timer);
	ENVADD(substr);
	ENVADD(about);
	ENVADD(array);
	ENVADD(delete);
	ENVADD(fecho);
	ENVADD(fread);
	ENVADD(strlen);
	ENVADD(sleep);
	ENVADD(aget);
	ENVADD(log);
	ENVADD(error);
	ENVADD(path);
	ENVADD(new);
	ENVADD(sprintf);
	ENVADD(if);
	ENVADD(switch);
	ENVADD(for);
	ENVADD(foreach);
	ENVADD(while);
	ENVADD(break);
	ENVADD(const);
	ENVADD(deconst);
	ENVADD(ls);
	ENVADD(mkdir);
	ENVADD(cd);
	ENVADD(cwdir);
	ENVADD(rm);
	ENVADD(cp);
	ENVADD(mv);
	ENVADD(matches);
	ENVADD(system);
	ENVADD(tell);
	ENVADD(source);
	ENVADD(return);
	#if defined(ORATR_USE_HTTP)
		ENVADD(http);
	#endif
	ENVADD(typedef);
	ENVADD(core);
#undef ENVADD
}

int main(int argc, char** argv)
{
	srand((unsigned)time(0));
	Environment env;
	initOratr(env);
	string config_filename = "";
#if defined(USING_LINUX)
	{
		stringstream pid_getter;
		pid_getter << "/proc/" << getpid() << "/exe";
		char buf[256];
		int len = readlink(pid_getter.str().c_str(), buf, sizeof(buf));
		if(len < 0) {
			// It has an error for some reason, and can't read the link
			cout << "Error: your system does not support /proc/<pid>/exe";
			cin.sync();
			cin.ignore();
			return 1;
		}
		buf[len] = '\0';
		config_filename = buf;
		config_filename = config_filename.substr(0,config_filename.find_last_of("/"));
	}
#elif defined(USING_WINDOWS)
	{
		char szAppPath[MAX_PATH] = "";
		string strAppName;
		
		GetModuleFileName(0, szAppPath, MAX_PATH);
		
		// Extract name
		strAppName = szAppPath;
		strAppName = strAppName.substr(0,strAppName.rfind("\\"));
		config_filename = strAppName;
	}
#elif defined(USING_NDS)
	PrintConsole topScreen;
	
	videoSetMode(MODE_0_2D);
	videoSetModeSub(MODE_0_2D);
	
	vramSetBankA(VRAM_A_MAIN_BG);
	vramSetBankC(VRAM_C_SUB_BG);
	
	consoleInit(&topScreen, 3,BgType_Text4bpp, BgSize_T_256x256, 31, 0, true, true);
	consoleSelect(&topScreen);
	
	Keyboard* kbd = keyboardDemoInit();
	kbd->OnKeyPressed = OnKeyPressed;
	// This line fixes printing bug caused by libnds 1.4.4 update
	setvbuf(stdout, NULL , _IONBF, 0);
	
	bool imfat = false;
	config_filename = argv[0];
	#if USE_FAT
	for(unsigned i=0;i<4;i++) {
		swiWaitForVBlank();
	}
	if(fatInitDefault()) {
		cout << "\033[42m*** Filesystem initialized! ***\033[47m";
		imfat=true;
	} else {
		cout << "\033[41m***Filesystem failed to load!***\033[47m";
		imfat=false;
	}
	#else
	disable_fileio(env);
	imfat = false;
	config_filename = "fat://data/OratrDS";
	#endif
#endif
	// Now that we have the program's location...
	env._paths.push_back(config_filename);
	env._paths.push_back(config_filename + PATHSEPARATOR + "std");
	config_filename += PATHSEPARATOR;
	config_filename += "config.orc";
	
	string full="";
#if defined(USING_NDS)
	if(imfat) {
#endif
		//Open config.orc file
		fstream config;
		config.open(config_filename.c_str());
		if(config.is_open()) {
			// There's a config.orc file, so run it.
			do{
				getline(config,full);
				try {
					orate(full, env, NOARGS, config);
				} catch (ErrorType e) {
					
				}
			}while(config.good());
			config.close();
		}
#if defined(USING_NDS)
	}
#endif
	
	vector<Variable>cmd;
	bool interactive = true;
	if(argc > 1) {
		for(int i=1;i<argc;i++) {
			if((!strcmp(argv[i],"-r")||!strcmp(argv[i],"-run"))) {
				if(argc > i+1) {
					stringstream ss;
					ss << "run `" << argv[++i] << "`";
					try {
						orate(ss.str(),env);
					} catch(ErrorType e) {
						
					}
					interactive = false;
				} else {
					_throw_error(ERROR_FILE);
				}
			} else if((!strcmp(argv[i],"-p")||!strcmp(argv[i],"-path")) && argv[i+1]) {
				string pathadder = "path add `" + (string)argv[++i] + "`";
				orate(pathadder,env);
			} else if(!strcmp(argv[i],"-i")) {
				interactive = true;
			} else if(string(argv[i]).substr(0,2) == (string)"-s") {
				string disables = string(argv[i]).substr(2);
				for(unsigned j=0;j<disables.size();j++) {
					switch(disables[j]) {
						case 'F': {
							env._func.erase("fread");
							env._func.erase("fecho");
							env._func.erase("path");
							env._func.erase("ls");
							env._func.erase("mkdir");
							env._func.erase("cd");
							env._func.erase("cwdir");
							env._func.erase("rm");
							env._func.erase("mv");
							env._func.erase("cp");
							env._func.erase("system");
							break;
						}
						case 'L': {
							env._func.erase("while");
							env._func.erase("for");
							env._func.erase("foreach");
							break;
						}
						case 'A': {
							env._func.erase("alias");
							env._func.erase("return");
							break;
						}
						case 'I': {
							env._func.erase("sleep");
							env._func.erase("get");
							break;
						}
						case 'H': {
							env._func.erase("http");
							break;
						}
					}
				}
			} else {
				stringstream ss;
				ss << "run `" << argv[i] << "`";
				try {
					orate(ss.str(),env);
				} catch(ErrorType e) {
					
				}
				interactive = false;
			}
		}
	}
	
	if(interactive) {
#if defined(ORATR_DEVELOPMENT)
		cout<< "\t\t+-----------------------------+\n"
		<< "\t\t| ORDEV " << __DATE__ << "  " << __TIME__ << " |\n"
		<< "\t\t+-----------------------------+\n";
#else
		cout<< "\t\t+------------------+\n"
		<< "\t\t|  ORATR " << orate("about version",env).str <<"  |\n"
		<< "\t\t+------------------+\n";
#endif
		while(1) {
			if(env.getvar("__prompt__").type != tInline) {
				cout << env.getvar("__prompt__").str;
			} else {
				cout << orate(env.getvar("__prompt__").str,env).str;
			}
			getline(cin,full);
			cout << env.getvar("__iosep__").str;
			try {
				cmd=tokenize(full,env);
				parse(cmd,env);
				if(env.getvar("__interactive__").i == true) {
					cout << env.getvar("__return__").str << endl;
				}
			} catch(ErrorType e) {
				if(e != ERROR_RETURNING) {
					_throw_error(e);
				}
			}
		}
	}
	return 0;
}
